#include "SimpleWnd.h"
#include <assert.h>
#include "GridCtrl.h"


#define IDC_INPLACE_EDIT      1
#define IDC_INPLACE_DROPLIST  2

//////////////////////////////////////////////////////////////////////
// implementation of the CInplaceEdit class.
//////////////////////////////////////////////////////////////////////

CInplaceEdit::CInplaceEdit()
{
	m_bStopped = FALSE;
}

void CInplaceEdit::SetParent(HWND hParent)
{
   assert (::IsWindow(hParent));
   m_hParent = hParent;
}

// starts editing. Creates EDIT control, sets coordinates, initial text and font
// Parameters:
//		rcCoords =	in, coordinates of EDIT control
//		szInitialText	=	in, initial text which will appear in EDIT after it has been created
//		hFont	=	in, HANDLE of font of the EDIT
// Return value:
//		true - the corresponding EDIT control has successfully created and ready for user input
//		false - some error occured during preparation of the EDIT control

BOOL CInplaceEdit::StartEditing(LPRECT rcCoords,  UINT uID, LPCTSTR szInitialText, HFONT hFont)
{
   assert (::IsWindow(m_hParent));
   assert (!::IsWindow(m_hWnd));

	HWND hWnd = ::CreateWindow(
		TEXT("EDIT"),
		NULL,
      WS_CHILD | ES_AUTOHSCROLL,
		rcCoords->left, rcCoords->top,
		rcCoords->right - rcCoords->left, rcCoords->bottom - rcCoords->top,
		m_hParent,
		(HMENU)uID,
		_Module.GetModuleInstance(),
		NULL);
   if(!hWnd) {
		return FALSE;
   }

	Attach(hWnd);
   if(hFont) {
		SendMessage(m_hWnd, WM_SETFONT, (WPARAM)hFont, FALSE);
   }
   if (szInitialText) {
	   SetWindowText(m_hWnd, szInitialText);
	   ::SendMessage(m_hWnd, EM_SETSEL, 0, -1);
   }
	ShowWindow(SW_SHOW);
	SetFocus();
	m_bStopped = FALSE;
	return TRUE;
}

LRESULT CInplaceEdit::OnKeyDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	bHandled = TRUE;
   if((int)wParam == VK_RETURN && !m_bStopped) {
		StopEditing(END_WITH_RETURNKEY);
   } else if((int)wParam == VK_ESCAPE && !m_bStopped) {
		StopEditing(END_WITH_CANCEL);
   } else {
		bHandled = FALSE;
   }
	return TRUE;
}

LRESULT CInplaceEdit::OnKillFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   if (!m_bStopped) {
		StopEditing(END_WITH_LEAVING);
   }
	return 0;
}

// terminates EDIT control and sends WM_ENDEDIT (see header file) message
//	to the parent window.
// Parameters:
//		uCode	= code which tells how a user has ended editing (see EM_ENDEDIT's WPARAM)
void CInplaceEdit::StopEditing(WPARAM uCode)
{
   if (m_bStopped) {
		return;
   }
	if(::IsWindow(m_hParent)) {
		m_bStopped = TRUE;
      int len = GetWindowTextLength(m_hWnd);
      LPTSTR pBuf = new TCHAR [len+1];
      if (!pBuf) {
         return;
      }
		GetWindowText(m_hWnd, pBuf, len+1);
		DestroyWindow();	// This will cause WM_KILLFOCUS
		SendMessage(m_hParent, WM_ENDEDIT, uCode, (LPARAM)pBuf);
      delete [] (pBuf);
	}
}

/////////////////////////////////////////////////////////////////
// Implementation for CInplaceDropList
/////////////////////////////////////////////////////////////////

CInplaceDroplist::CInplaceDroplist()
{
   m_bStopped = FALSE;
}

void CInplaceDroplist::SetParent(HWND hParent)
{
   assert (::IsWindow(hParent));
   m_hParent = hParent;
}

BOOL CInplaceDroplist::StartSelecting(	LPRECT rcCoords, UINT uID, 
                                      LPCTSTR szInitText, LPCTSTR* pszItems,
						                    HFONT hFont)
{
   assert (::IsWindow(m_hParent));
   assert (!::IsWindow(m_hWnd));
   assert (pszItems);

   HWND hCombo = ::CreateWindow(
               _T("COMBOBOX"),
               NULL,
               WS_CHILD | CBS_DROPDOWNLIST |WS_VSCROLL,
               rcCoords->left, rcCoords->top,
               rcCoords->right - rcCoords->left, rcCoords->bottom - rcCoords->top + 200,
               m_hParent,
               (HMENU)uID,
               _Module.GetModuleInstance(),
               NULL);

   if(hCombo == NULL) {
      return FALSE;
   }

   Attach(hCombo);
   if(hFont) {
		SendMessage(m_hWnd, WM_SETFONT, (WPARAM)hFont, FALSE);
   }

   int idxSel = -1;
   int i = 0;
	while (pszItems[i]) {
      if (idxSel < 0 && szInitText && (_tcscmp(pszItems[i], szInitText) == 0)) {
         idxSel = i;
      }
		::SendMessage(m_hWnd, CB_ADDSTRING, 0, (LPARAM)pszItems[i]);
		i++;
	}
	
   if (idxSel < 0) {
		idxSel = 0;
   }
	
	::SendMessage(m_hWnd, CB_SETCURSEL, (WPARAM)idxSel, 0);
   ShowWindow(SW_SHOW);
	SetFocus();
	m_bStopped = FALSE;
	return TRUE;
}

LRESULT CInplaceDroplist::OnKillFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   if (!m_bStopped) {
		StopSelecting(END_WITH_LEAVING);
   }
	return 0;
}

void CInplaceDroplist::StopSelecting(WPARAM uCode)
{
   if (m_bStopped) {
		return;
   }
	if(::IsWindow(m_hParent)) {
		m_bStopped = TRUE;
      int len = GetWindowTextLength(m_hWnd);
      LPTSTR pBuf = new TCHAR [len+1];
      if (!pBuf) {
         return;
      }
		GetWindowText(m_hWnd, pBuf, len+1);
		DestroyWindow();	// This will cause WM_KILLFOCUS
		SendMessage(m_hParent, WM_ENDEDIT, uCode, (LPARAM)pBuf);
      delete [] (pBuf);
	}
}

LRESULT CInplaceDroplist::OnKeyDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	bHandled = TRUE;
   if((int)wParam == VK_RETURN && !m_bStopped) {
		StopSelecting(END_WITH_RETURNKEY);
   } else if((int)wParam == VK_ESCAPE && !m_bStopped) {
		StopSelecting(END_WITH_CANCEL);
   } else {
		bHandled = FALSE;
   }
	return TRUE;
}

LRESULT CInplaceDroplist::OnGetDlgCode(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	return DLGC_WANTALLKEYS;
}

/////////////////////////////////////////////////////////////////
// Implementation for CGridCtrl
/////////////////////////////////////////////////////////////////
CGridCtrl::CGridCtrl()
{
	m_hParent = NULL;
	m_rcSize.left = 0;
	m_rcSize.right = 0;
	m_rcSize.top = 0;
	m_rcSize.bottom = 0;
	m_nCols = -1;
	m_nRows = 1;
	m_nHOffset = 0;
	m_nFirstRow = 1;
	m_nCurRow = m_nPrevRow = -1;	//Nothing Selected.
   m_nCurCol = m_nCurCol = -1; 
	m_bResize = TRUE;
	m_nStatus = VIEWER_STATUS_NORMAL;
	m_nPrevSizingLine = -1;
	m_nSizedCol = -1;
   m_nRowHeight = -1;
   m_bEnabled = true;

	m_hbrHdrBk = (HBRUSH)(::GetSysColor(COLOR_WINDOWFRAME) +1);
	m_hbrNormal = (HBRUSH)(GetSysColor(COLOR_WINDOW)+1);
	m_clrLine = ::GetSysColor(COLOR_3DLIGHT);
	m_clrText = ::GetSysColor(COLOR_WINDOWTEXT);
   m_clrDisabledText = ::GetSysColor(COLOR_3DSHADOW);
	m_clrBrightLine = ::GetSysColor(COLOR_3DHILIGHT);
	m_clrDarkLine = ::GetSysColor(COLOR_3DSHADOW);
	m_penLine = CreatePen(PS_SOLID, 1, m_clrLine);
	m_penBright = CreatePen(PS_SOLID, 1, m_clrBrightLine);
	m_penDark = CreatePen(PS_SOLID, 1, m_clrDarkLine);
	m_clrHilitText = GetSysColor(COLOR_HIGHLIGHTTEXT);
	m_hbrHilit = GetSysColorBrush(COLOR_HIGHLIGHT);

	m_pColWidth = NULL;
	m_pColPos = NULL;
	m_pColAlign = NULL;
}

CGridCtrl::~CGridCtrl()
{
	::DeleteObject(m_penLine);
	::DeleteObject(m_penBright);
	::DeleteObject(m_penDark);

   if (m_pColWidth) {
		delete[] m_pColWidth;
   }
   if (m_pColPos) {
		delete[] m_pColPos;
   }
   if (m_pColAlign) {
		delete[] m_pColAlign;
   }

   for (int i = 0; i < m_nRows * m_nCols ; i++) {
      if (m_vecString[i]) {
			delete[] m_vecString[i];
      }
	}
}

int CGridCtrl::SetCols(int nCols)
{
   assert (nCols > 0);
   if (m_nCols >= 0) {
      return -1;
   }
   m_nCols = nCols;

	m_pColWidth = new int[nCols];
	m_pColPos = new int[nCols];
	m_pColAlign = new UINT[nCols];
	m_vecString.resize(nCols);
	for (int i = 0; i < nCols ; i++) {
		m_pColWidth[i] = 75;
		m_pColAlign[i] = DT_LEFT;
		m_vecString[i] = NULL;
      if (i == 0) {
			m_pColPos[0] = m_pColWidth[0];
      } else {
			m_pColPos[i] = m_pColPos[i-1] + m_pColWidth[i];
      }
	}
   return 0;
}

HWND CGridCtrl::CreateGridCtrl (HWND hParent, RECT& rc, DWORD style, DWORD styleex, UINT nID)
{
   m_nID = nID;
   m_hParent = hParent;
   m_bEnabled = !(style & WS_DISABLED);
   return Create (hParent, rc, NULL, style | WS_CHILD | WS_VISIBLE, styleex, nID);
}

LRESULT CGridCtrl::OnCreate(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	RECT rc;
   
   m_edit.SetParent(m_hWnd);
   m_droplist.SetParent(m_hWnd);
   if (m_nRowHeight < 0) {
      m_nRowHeight = GetRowHeight();
   }

	GetClientRect(&rc);
	m_rcSize.right = rc.right - rc.left;
	m_rcSize.bottom = rc.bottom - rc.top;
	// I want scroll bars
   LONG lStyle = ::GetWindowLong(m_hWnd, GWL_STYLE);
	lStyle &= ~WS_OVERLAPPED;
	lStyle &= ~WS_MAXIMIZEBOX;
	lStyle |= (WS_VSCROLL|WS_HSCROLL|WS_TABSTOP);
   ::SetWindowLong(m_hWnd, GWL_STYLE, lStyle);
	// proportional
	SCROLLINFO si;
	si.cbSize = sizeof(SCROLLINFO);
	si.fMask = SIF_PAGE;
	si.nPage = 1;
	::SetScrollInfo(m_hWnd, SB_VERT, &si, TRUE);
	::SetScrollInfo(m_hWnd, SB_HORZ, &si, TRUE);
	::SetClassLong(m_hWnd, GCL_HBRBACKGROUND, COLOR_WINDOW + 1);
	
   m_bHScrolled = false;
   m_bVScrolled = false;
	SetScrollBars();
	return 0;
}

LRESULT CGridCtrl::OnPaint(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	PAINTSTRUCT ps;
	RECT rcPaint;
	RECT rcCells;	// the cells to be drawn

	BeginPaint(&ps);
	CopyRect(&rcPaint, &ps.rcPaint);
	
	POINT pt;
	pt.x = rcPaint.left;
	pt.y = rcPaint.top;
	Point2Cell(pt);
	rcCells.left = pt.x;
	rcCells.top = pt.y;
	pt.x = rcPaint.right;
	pt.y = rcPaint.bottom;
	Point2Cell(pt);
	rcCells.right = pt.x;
	rcCells.bottom = pt.y;

	DrawCells(ps.hdc, rcCells);

	EndPaint(&ps);
	return 0;
}

LRESULT CGridCtrl::OnScroll(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	WORD wCode = LOWORD(wParam);
   if (wCode == SB_ENDSCROLL) {
		return S_OK;
   }
   
   SetFocus();
	int nBar = (nMsg == WM_HSCROLL ? SB_HORZ : SB_VERT);
	long lPos = GetScrollPos(nBar);
	int nMinX, nMaxX, nMinY, nMaxY;
	RECT rc;

	GetClientRect(&rc);
	int nVisibleRows = (rc.bottom - rc.top) / m_nRowHeight;

	GetScrollRange(SB_HORZ, &nMinX, &nMaxX);
	GetScrollRange(SB_VERT, &nMinY, &nMaxY);

	if (nMsg == WM_HSCROLL) {
		switch (wCode) {
		case SB_LINELEFT:
         if (lPos > nMinX) {
				lPos --;
				m_nHOffset = lPos;
			}
			break;
		case SB_PAGELEFT:
			if (lPos > nMinX) {
				lPos -= 10;
            if (lPos < nMinX) {
					lPos = nMinX;
            }
				m_nHOffset = lPos;
			}
			break;
		case SB_LINERIGHT:
			if (lPos < nMaxX) {
				lPos ++;
				m_nHOffset  = lPos;
			}
			break;
		case SB_PAGERIGHT:
			if (lPos < nMaxX) {
				lPos += 10;
            if (lPos > nMaxX) {
					lPos = nMaxX;
            }
				m_nHOffset = lPos;
			}
			break;
		case SB_THUMBPOSITION:
		case SB_THUMBTRACK:
			lPos = HIWORD(wParam);
			m_nHOffset = lPos;
			break;
		}
   } else {
		switch (wCode) {
		case SB_LINELEFT:
			if (lPos > nMinY) {
				lPos --;
				m_nFirstRow = lPos + 1;
			}
			break;
		case SB_PAGELEFT:
			nVisibleRows --; //Exculde the header
			m_nFirstRow -= nVisibleRows;
         if (m_nFirstRow < 1) {
				m_nFirstRow = 1;
         }
			lPos = m_nFirstRow -1;
			break;
		case SB_LINERIGHT:
			if (lPos < nMaxY) {
				lPos ++;
				m_nFirstRow = lPos + 1;
			}
			break;
		case SB_PAGERIGHT:
			nVisibleRows --; //Exculde the header
			m_nFirstRow += nVisibleRows;
			if (m_nFirstRow > nMaxY) {
				m_nFirstRow = nMaxY;
				m_nFirstRow ++;
			}
			lPos = m_nFirstRow -1;
			break;
//		case SB_THUMBPOSITION:
		case SB_THUMBTRACK:
			lPos = HIWORD(wParam);
			m_nFirstRow = lPos + 1;
			break;
		}
	}
	SetScrollPos(nBar, lPos, TRUE);
	Invalidate(FALSE);

	return 0;
}

void CGridCtrl::DrawCells(HDC hDC, RECT& rcCells)
{
	int nTop;
	int nBottom;
	int nLeft, nRight;
	int nLeft0, nRight0;
	RECT rcClient;

	GetClientRect(&rcClient);

	int l, r, t, b;
	l = rcCells.left;
	r = rcCells.right;
	t = rcCells.top;
	b = rcCells.bottom;

	if ( t == 0) {
		DrawHeader(hDC, l, r);
		t++;
	}
   if ( t < m_nFirstRow) {
		t = m_nFirstRow;
   }

	//get the start col's left & right coordinates
	nLeft0 = -m_nHOffset;
   for (int nCol = 0; nCol < l; nCol++) {
		nLeft0 += m_pColWidth[nCol];
   }
   if (l < m_nCols) {
		nRight0 = nLeft0 + m_pColWidth[l];
   } else {
		nRight0 = rcClient.right;
   }
	nTop = (t - m_nFirstRow + 1) * m_nRowHeight;
	nBottom = nTop + m_nRowHeight -1;

	for (int i = t; i <= b; i++) {
		nLeft = nLeft0;
		nRight = nRight0;
		for (int j = l; j <= r; j++) {
			RECT rc;
			rc.left = nLeft;
			rc.right = nRight;
			rc.top = nTop;
			rc.bottom = nBottom + 1;
         if (!m_bEnabled) {
            SetTextColor (hDC, m_clrDisabledText);
				FillRect(hDC, &rc, m_hbrNormal);
         } else if (m_nCurRow != i) {
				SetTextColor(hDC, m_clrText);
				FillRect(hDC, &rc, m_hbrNormal);
         } else {
				SetTextColor(hDC, m_clrHilitText);
				FillRect(hDC, &rc, m_hbrHilit);
            if (m_nCurCol == j && m_hWnd == GetFocus()) {
               DrawFocus(hDC, &rc);
            }
			}
			//Draw bottom line
			HPEN hPrevPen = (HPEN)SelectObject(hDC, m_penLine);
			MoveToEx(hDC, nLeft, nBottom, NULL);
			LineTo(hDC, nRight-1, nBottom);
			//Draw right line
         if (j < m_nCols) {
				LineTo(hDC, nRight-1, nTop-1);
         }
			//Draw text
			if (j < m_nCols && i < m_nRows && m_vecString[i*m_nCols+j]) {
				RECT rcTmp;
				rcTmp.left = nLeft + 4;
				rcTmp.right = nRight - 4;
				rcTmp.top = nTop;
				rcTmp.bottom = nBottom;
				UINT uAlign = DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE | DT_END_ELLIPSIS;
				uAlign |= m_pColAlign[j];
				SetBkMode(hDC, TRANSPARENT);
				::DrawText(hDC, m_vecString[i*m_nCols+j], -1, &rcTmp, uAlign);
			}
			SelectObject(hDC, hPrevPen);

         if (j < m_nCols) {
 				nLeft += m_pColWidth[j];
         }
         if (j + 1 < m_nCols) {
				nRight = nLeft + m_pColWidth[j+1];
         } else {
				nRight = rcClient.right;
         }
		}
		nTop += m_nRowHeight;
		nBottom += m_nRowHeight;
	}
}

void CGridCtrl::DrawHeader(HDC hDC, int l, int r)
{
	int nTop = 0;
	int nBottom = m_nRowHeight - 1;
	int nLeft, nRight;
	RECT rcClient;

	GetClientRect(&rcClient);
	nLeft = -m_nHOffset;
   for (int nCol = 0; nCol < l; nCol++) {
		nLeft += m_pColWidth[nCol];
   }

   if (l < m_nCols) {
		nRight = nLeft + m_pColWidth[l];
   } else {
		nRight = rcClient.right;
   }

	for (int i = l; i <= r; i++) {
		RECT rc;
		rc.left = nLeft;
		rc.right = nRight;
		rc.top = 0;
		rc.bottom = nBottom + 1;
		FillRect(hDC, &rc, m_hbrHdrBk);
		// Draw the bottom
		HPEN hPrevPen = (HPEN)SelectObject(hDC, m_penDark);
		MoveToEx(hDC, nLeft, nBottom, NULL);
		LineTo(hDC, nRight, nBottom);
		// Draw the seperator
		if (i < m_nCols) {
			MoveToEx(hDC, nRight-2, nTop+2, NULL);
			LineTo(hDC, nRight-2, nBottom-2);
			SelectObject(hDC, m_penBright);
			LineTo(hDC, nRight-1, nBottom-2);
			LineTo(hDC, nRight-1, nTop+2);
			LineTo(hDC, nRight-2, nTop+2);
		}
		// Draw Txet
		if (i < m_nCols && m_vecString[i]) {
			RECT rcTmp;
			rcTmp.left = nLeft + 4;
			rcTmp.right = nRight - 4;
			rcTmp.top = nTop;
			rcTmp.bottom = nBottom;
			UINT uAlign = DT_VCENTER | DT_NOPREFIX | DT_SINGLELINE | DT_END_ELLIPSIS;
			uAlign |= m_pColAlign[i];
         if (!m_bEnabled) {
            SetTextColor(hDC, m_clrDisabledText);
         } else {
			   SetTextColor(hDC, m_clrText);
         }
			SetBkMode(hDC, TRANSPARENT);
         ::DrawText(hDC, m_vecString[i], -1, &rcTmp, uAlign);
		}
		SelectObject(hDC, hPrevPen);

      if (i < m_nCols) {
			nLeft += m_pColWidth[i];
      }

      if (i + 1 < m_nCols) {
			nRight = nLeft + m_pColWidth[i+1];
      } else {
			nRight = rcClient.right;
      }
	}
}

void CGridCtrl::Point2Cell(POINT& pt)
{
	int nRow, nCol;
	int nColLeft, nColRight;
	nRow = pt.y / m_nRowHeight;
   if (nRow > 0) {
		nRow += (m_nFirstRow -1);
   }

	nCol = -1;
	nColLeft = 0;
	for (int i = 0; i < m_nCols; i++) {
		nColRight = nColLeft + m_pColWidth[i];
		if (pt.x + m_nHOffset >= nColLeft && pt.x + m_nHOffset < nColRight) {
			nCol = i;
			break;
		}
		nColLeft = nColRight;
	}

   if (nCol == -1) {
		nCol = m_nCols;
   }

	pt.x = nCol;
	pt.y = nRow;
}

int CGridCtrl::GetRows()
{
	return m_nRows;
}

int CGridCtrl::GetCols()
{
	return m_nCols;
}

void CGridCtrl::InsertRows(int nStart, int nCount)
{
   if (nCount < 1) {
		return;
   }

   if (nStart < 1) {
		nStart = 1;
   }

	m_vecString.resize((m_nRows + nCount) * m_nCols);

	// Move the Strings
   int i;
   for (i = m_nRows*m_nCols-1 ; i >= nStart*m_nCols; i--) {
		m_vecString[i+nCount*m_nCols] = m_vecString[i];
   }

	// Allocate new strings
	for (i = nStart*m_nCols ; i < (nStart + nCount)*m_nCols; i++) {
		m_vecString[i] = NULL;
	}
	m_nRows += nCount;
	SetScrollBars();
   Invalidate(FALSE);
}

void CGridCtrl::DeleteRows(int nStart, int nCount)
{
   if (nStart < 1) {
		nStart = 1;
   }

   if (nStart > m_nRows) {
		return;
   }

   if (nCount < 1) {
		nCount = m_nCols -1;
   }

   if (nCount + nStart > m_nRows) {
		nCount = m_nRows - nStart;
   }
	
   int nLastLine = nStart + nCount;
	//Delete the strings
   int i;
	for (i = nStart*m_nCols; i < nLastLine*m_nCols; i++) {
		LPTSTR p = m_vecString[i];
      if (p) {
			delete[] p;
      }
		m_vecString[i] = NULL;
	}
	//Move strings
   for (i = nLastLine * m_nCols ; i < m_nRows*m_nCols; i++) {
		m_vecString[i - nCount*m_nCols] = m_vecString[i];
   }

	m_vecString.resize( (m_nRows-nCount)*m_nCols );
	m_nRows -= nCount;
   if (m_nCurRow >= m_nRows) {
      m_nCurRow = m_nCurCol = -1;
   }
   SetScrollBars();
   Invalidate(FALSE);
}

void CGridCtrl::SetString(int nRow, int nCol, LPCTSTR szText)
{
   assert (nRow < m_nRows && nRow >= 0 && nCol < m_nCols && nCol >= 0);
   if (m_vecString[nRow * m_nCols + nCol]) {
      delete [] (m_vecString[nRow * m_nCols + nCol]);
   }
   size_t len = lstrlen(szText);
   m_vecString[nRow * m_nCols + nCol] = new TCHAR [len+1];
	lstrcpy(m_vecString[nRow * m_nCols + nCol], szText);

   POINT cell;
   RECT rc;
   cell.x = nCol; cell.y = nRow;
   GetCellRect(&cell, &rc);
   InvalidateRect(&rc, FALSE);
}

LPCTSTR CGridCtrl::GetString(int nRow, int nCol)
{
   assert (nRow < m_nRows && nRow >= 0 && nCol < m_nCols && nCol >= 0);
   return m_vecString[nRow * m_nCols + nCol];
}

void CGridCtrl::SetCurCellString(LPCTSTR szText)
{
   SetString(m_nCurRow, m_nCurCol, szText);
}

LPCTSTR CGridCtrl::GetCurCellString()
{
   if (! (m_nCurRow < m_nRows && m_nCurRow >= 0 && m_nCurCol < m_nCols && m_nCurCol >= 0)) {
      return NULL;
   }
   return GetString(m_nCurRow, m_nCurCol);
}

void CGridCtrl::SetRowHeight(int nHeight)
{
   if (nHeight == m_nRowHeight) {
		return;
   }
	if (!::IsWindow(m_hWnd)) {
		m_nRowHeight = nHeight;
		return;
	}

   if (nHeight <= 0) {
      m_nRowHeight = GetRowHeight();
   } else {
      m_nRowHeight = nHeight;
   }
   SetScrollBars();
   Invalidate(FALSE);
}

void CGridCtrl::SetColWidth(int nCol, int nWidth)
{
   if (nWidth <= 0 || nCol < 0 || nCol >= m_nCols) {
		return;
   }

	if (!::IsWindow(m_hWnd)) {
		m_pColWidth[nCol] = nWidth;
		return;
	}

   if (nWidth == m_pColWidth[nCol]) {
		return;
   }
   
   m_pColWidth[nCol] = nWidth;
   SetScrollBars();

	m_pColPos[0] = m_pColWidth[0];
   for (int i = 1; i < m_nCols; i++) {
		m_pColPos[i] = m_pColPos[i-1] + m_pColWidth[i];
   }
	Invalidate(FALSE);
}

LRESULT CGridCtrl::OnGetDlgCode(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	return DLGC_WANTARROWS;
}

LRESULT CGridCtrl::OnKeyDown(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	switch (wParam) {
	case VK_LEFT:
      return OnVKLeft();

	case VK_RIGHT:
      return OnVKRight();

   case VK_UP:
      return OnVKUp();

   case VK_DOWN:
      return OnVKDown();

	case VK_NEXT:
      return OnVKNext();

   case VK_PRIOR:
      return OnVKPrior();

   case VK_RETURN:
      if (m_nCurRow >= 0 && m_nCurCol >= 0) {
         ::PostMessage(m_hParent, GDM_ENTER_PRESSED, m_nID, MAKELONG(m_nCurCol, m_nCurRow));
      }
	}
	return 0;
}

LRESULT CGridCtrl::OnMouseEvents(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	POINT pt;
	pt.x = LOWORD(lParam);
	pt.y = HIWORD(lParam);
	RECT rc;
	HWND hFocus;
	HCURSOR hCur = NULL;

	switch (nMsg) {
   case WM_LBUTTONDBLCLK:
      Point2Cell(pt);
      if (pt.x == m_nCurCol && pt.y == m_nCurRow) {
         ::PostMessage(m_hParent, GDM_DBL_CLICK, m_nID, MAKELONG(m_nCurCol, m_nCurRow));
      }
      break;
	case WM_LBUTTONDOWN:
		hFocus = ::GetFocus();
      if (hFocus != m_hWnd) {
			SetFocus();
      }
		if (m_nStatus == VIEWER_STATUS_NORMAL) {
			Point2Cell(pt);
         SelectCell(pt);
      } else if (m_nStatus == VIEWER_STATUS_READY_SIZE) {
			GetClientRect(&rc);
			SetCapture();
			DrawSizingLine(pt.x, rc.top, rc.bottom);
			hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZEWE));
			m_nStatus = VIEWER_STATUS_SIZING;
			m_nPrevSizingLine = pt.x;
		}
		break;

	case WM_RBUTTONDOWN:
		hFocus = ::GetFocus();
      if (hFocus != m_hWnd) {
			SetFocus();
      }
		m_nStatus = VIEWER_STATUS_NORMAL;
		m_nPrevSizingLine = -1;
		break;

	case WM_LBUTTONUP:
		if (m_nStatus == VIEWER_STATUS_SIZING) {
			int nTmp;
			GetClientRect(&rc);
			DrawSizingLine(m_nPrevSizingLine, rc.top, rc.bottom);
			ReleaseCapture();
			nTmp = pt.x;
         if (nTmp >= 32768) {
				nTmp -= 65536;
         }
			
         if (nTmp < m_nSizeLeftLimit - m_nHOffset) {
				nTmp = m_nSizeLeftLimit - m_nHOffset;
         }
         if (m_nSizedCol != 0) {
				SetColWidth(m_nSizedCol, nTmp + m_nHOffset - m_pColPos[m_nSizedCol -1]);
         } else {
				SetColWidth(0, nTmp + m_nHOffset);
         }
		}
		m_nStatus = VIEWER_STATUS_NORMAL;
		m_nPrevSizingLine = -1; 
		break;
	case WM_RBUTTONUP:
		m_nStatus = VIEWER_STATUS_NORMAL;
		m_nPrevSizingLine = -1;
		break;

	case WM_MOUSEMOVE:
      if (!m_bResize) {
			break;
      }
		if (m_nStatus != VIEWER_STATUS_SIZING) {
			m_nStatus = VIEWER_STATUS_NORMAL;
			GetClientRect(&rc);
			if (pt.y > rc.top && pt.y < m_nRowHeight) {
				for (int i = 0; i < m_nCols; i++) {
					if (pt.x >= m_pColPos[i]-3-m_nHOffset && pt.x <= m_pColPos[i]+3-m_nHOffset) {
						hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZEWE));
						m_nStatus = VIEWER_STATUS_READY_SIZE;
						m_nSizedCol = i;
						m_nSizeLeftLimit = m_pColPos[i] - m_pColWidth[i] + 4; 
						break;
					}
				}
			}
      } else if (m_nStatus == VIEWER_STATUS_SIZING) {
			GetClientRect(&rc);
			int nTmp;
			DrawSizingLine(m_nPrevSizingLine, rc.top, rc.bottom);
			if (pt.x >= m_nSizeLeftLimit - m_nHOffset) {
				DrawSizingLine(pt.x, rc.top, rc.bottom);
				m_nPrevSizingLine = pt.x;
            if (m_nPrevSizingLine >= 32768) {
					m_nPrevSizingLine -= 65536;
            }
         } else {
				nTmp = m_nSizeLeftLimit - m_nHOffset;
				DrawSizingLine(nTmp, rc.top, rc.bottom);
				m_nPrevSizingLine = nTmp;
			}
			hCur = LoadCursor(NULL, MAKEINTRESOURCE(IDC_SIZEWE));
		}
		break;
	}
   if (!hCur) {
		hCur = LoadCursor(NULL, IDC_ARROW);
   }
	SetCursor(hCur);
	return 0;
}

LRESULT CGridCtrl::OnFocus(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   RECT rc;
	switch (nMsg) {
	case WM_SETFOCUS:
      m_hbrHilit = GetSysColorBrush(COLOR_HIGHLIGHT);
		break;
	case WM_KILLFOCUS:
      m_hbrHilit = GetSysColorBrush(COLOR_INACTIVECAPTION);
		break;
	}
   GetRowRect(m_nCurRow, &rc);
   InvalidateRect(&rc, FALSE);
	return 0;
}

LRESULT CGridCtrl::OnEnable(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   if ((!m_bEnabled && !wParam) || (m_bEnabled && wParam)) {
      return 0;
   }

   m_bEnabled = (wParam != false);
   if (!m_bEnabled) {
      m_nCurRow = m_nCurCol = -1;
   }
   InvalidateRect (NULL, FALSE);
   return 0;
}


LRESULT CGridCtrl::OnSetFont(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   m_pfnDefault(m_hWnd, nMsg, wParam, lParam);
   SetScrollBars();
   bHandled = TRUE;
   Invalidate(FALSE);
   return 0;
}

LRESULT CGridCtrl::OnSize(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
   SetScrollBars();
   Invalidate(FALSE);
   return 0;
}

LRESULT CGridCtrl::OnEndEdit(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
  ::SendMessage(m_hParent, GDM_END_EDITING, MAKELONG(m_nID, wParam) , lParam);
	return 0;
}

void CGridCtrl::SetResize(BOOL bResize)
{
	m_bResize = bResize;
}

int CGridCtrl::GetRowHeight()
{
   assert (::IsWindow(m_hWnd));
   HDC dc = GetDC();
   TEXTMETRIC tm;
   GetTextMetrics(dc, &tm);
   ReleaseDC(dc);
   return tm.tmHeight + 2;
}

void CGridCtrl::DrawSizingLine(int x, int y0, int y1)
{
	HDC hDC = GetDC();
	int iOldROP2 = ::SetROP2(hDC, R2_NOT);
	RECT rcClient;

	GetClientRect(&rcClient);
	::MoveToEx(hDC, x, y0, NULL);
	::LineTo(hDC, x, y1);
	::SetROP2(hDC, iOldROP2);
	ReleaseDC(hDC);
}

void CGridCtrl::SetColAlign(UINT* puAlign, int nCol0, int nCount)
{
	int nCol1 = nCol0+nCount > m_nCols ? m_nCols : nCol0+nCount;
	for (int i = nCol0; i < nCol1 ; i++) {
		switch (puAlign[i])
		{
		case VIEWER_TEXT_LEFT:
			m_pColAlign[i] = DT_LEFT;
			break;
		case VIEWER_TEXT_CENTER:
			m_pColAlign[i] = DT_CENTER;
			break;
		case VIEWER_TEXT_RIGHT:
			m_pColAlign[i] = DT_RIGHT;
			break;
		default:
			m_pColAlign[i] = DT_LEFT;
			break;
		}
	}
   if (::IsWindow(m_hWnd)) {
		Invalidate(FALSE);
   }
}

void CGridCtrl::EditCurCell()
{
	RECT rc;
	POINT ptCell;

	ptCell.x = m_nCurCol;
	ptCell.y = m_nCurRow;
   SetCellVisible(ptCell);

	GetCellRect(&ptCell, &rc);
	rc.bottom--; rc.right--;

	LPTSTR ptszTxt = m_vecString[m_nCurRow * m_nCols + m_nCurCol];
	if(m_edit.StartEditing(&rc, IDC_INPLACE_EDIT, ptszTxt, NULL)) {
      ::PostMessage(m_hParent,
						GDM_START_EDITING,
						(WPARAM)m_nID, 
						(LPARAM)MAKELONG(m_nCurCol, m_nCurRow));
	}
}

void CGridCtrl::DroplistCurCell(LPCTSTR* pszItems)
{
	RECT rc;
	POINT ptCell;

	ptCell.x = m_nCurCol;
	ptCell.y = m_nCurRow;
   SetCellVisible(ptCell);

	GetCellRect(&ptCell, &rc);
	rc.bottom--; rc.right--;

	LPTSTR ptszTxt = m_vecString[m_nCurRow * m_nCols + m_nCurCol];
	if (m_droplist.StartSelecting(&rc, IDC_INPLACE_DROPLIST, ptszTxt, pszItems, NULL)) {
      ::PostMessage(m_hParent,
						GDM_START_EDITING,
						(WPARAM)m_nID, 
						(LPARAM)MAKELONG(m_nCurCol, m_nCurRow));
	}
}

void CGridCtrl::SetScrollBars()
{
   assert (::IsWindow(m_hWnd));

   int nScrollW, nScrollH;
	nScrollH = GetSystemMetrics(SM_CYHSCROLL);
	nScrollW = GetSystemMetrics(SM_CXVSCROLL);
	RECT rc;
   GetClientRect(&rc);

   int nTotalWidth = GetTotalWidth();
   int nTotalHeight = m_nRowHeight * m_nRows;

   if (nTotalWidth > rc.right - rc.left) {
      // If the rect is not wide enough
      SetScrollRange (SB_HORZ, 0, nTotalWidth - rc.right + rc.left, FALSE);
      SetScrollPos(SB_HORZ, m_nHOffset, TRUE);
      ShowScrollBar (SB_HORZ, TRUE);
      if (!m_bHScrolled) {
         rc.bottom -= nScrollH;
         if (rc.bottom < rc.top) {
            rc.bottom = rc.top;
         }
      } 
      m_bHScrolled = true;
   } else {
      if (m_bHScrolled) {
         rc.bottom += nScrollH;
         m_bHScrolled = false;
         m_nHOffset = 0;
      }
   }
   
   if (nTotalHeight > rc.bottom - rc.top) {
      // The RECT is not high enough.
      SetScrollRange (SB_VERT, 0, m_nRows - (rc.bottom - rc.top)/m_nRowHeight, FALSE);
      ShowScrollBar (SB_VERT, TRUE);
      if (!m_bVScrolled) {
         rc.right -= nScrollW;
         if (rc.right < rc.left) {
            rc.right  = rc.left;
         }
      }
      m_bVScrolled = true;

      if (nTotalWidth > rc.right - rc.left) {
         SetScrollRange (SB_HORZ, 0, nTotalWidth - rc.right + rc.left, FALSE);
         SetScrollPos(SB_HORZ, m_nHOffset, TRUE);
         ShowScrollBar (SB_HORZ, TRUE);
         m_bHScrolled = true;
      }
      SetScrollPos (SB_VERT, m_nFirstRow-1, TRUE);
   } else {
      if (m_bVScrolled) {
         rc.right += nScrollW;
         m_bVScrolled = false;
         m_nFirstRow = 1;
      }
   }

   if (!m_bHScrolled) {
      SetScrollRange (SB_HORZ, 0, 0, FALSE);
      ShowScrollBar (SB_HORZ, FALSE);
   }

   if (!m_bVScrolled) {
      SetScrollRange (SB_VERT, 0, 0, FALSE);
      ShowScrollBar (SB_VERT, FALSE);
   }
}

int CGridCtrl::GetTotalWidth()
{
   int nWidth  = 0;
   for (int i = 0; i < m_nCols; i++) {
		nWidth += m_pColWidth[i];
   }
   return nWidth;
}

void CGridCtrl::DrawFocus(HDC hDC, RECT* prc)
{
   long l,r,t,b;
   l = prc->left;
   r = prc->right -2;
   t = prc->top;
   b = prc->bottom - 2;

   int iOldROP2 = ::SetROP2(hDC, R2_NOT);
   HPEN hPen = ::CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
   HPEN hOldPen = (HPEN)::SelectObject(hDC, hPen);
   MoveToEx(hDC, l, t, NULL);
   LineTo(hDC, r, t);
   LineTo(hDC, r, b);
   LineTo(hDC, l, b);
   LineTo(hDC, l, t);
   SelectObject(hDC, hOldPen);
   ::SetROP2(hDC, iOldROP2);
}

void CGridCtrl::SelectCell(const POINT& cell)
{
   if (! ((cell.y > 0 && cell.y < m_nRows) && (cell.x >= 0 && cell.x < m_nCols)) ){
      return;
   }

   m_nPrevRow = m_nCurRow;
   m_nPrevCol = m_nCurCol;
   m_nCurRow = cell.y;
   m_nCurCol = cell.x;
   SetCellVisible(cell);
   ::PostMessage(m_hParent, GDM_ENTER_CELL, (WPARAM)m_nID, (LPARAM)MAKELONG(m_nCurCol, m_nCurRow)); 
}

void CGridCtrl::SetCellVisible(const POINT& cell)
{
   RECT rc, rcClient;
   bool bRedrawAll = false;

   GetClientRect(&rcClient);
   GetCellRect(&cell, &rc);

   if (rc.left < rcClient.left) {
      bRedrawAll = true;
      m_nHOffset = m_pColPos[cell.x] - m_pColWidth[cell.x];
      SetScrollPos(SB_HORZ, m_nHOffset, TRUE);
   } else if (rc.right > rcClient.right) {
      bRedrawAll = true;
      m_nHOffset -= (rcClient.right - rc.right);
      SetScrollPos(SB_HORZ, m_nHOffset, TRUE);
   }

   int nVisibleRows = (rcClient.bottom - rcClient.top) / m_nRowHeight;
   nVisibleRows --; // exclude the header line.

   if (cell.y < m_nFirstRow) {
      bRedrawAll = true;
      m_nFirstRow = cell.y;
      SetScrollPos(SB_VERT, m_nFirstRow-1, TRUE);
   } else if (cell.y - m_nFirstRow >= nVisibleRows) {
      bRedrawAll = true;
      m_nFirstRow = cell.y - nVisibleRows + 1;
      SetScrollPos(SB_VERT, m_nFirstRow-1, TRUE);
   }
   if (bRedrawAll) {
      Invalidate(FALSE);
      return;
   }

   if (m_nPrevRow >= 1 && m_nPrevRow < m_nRows) {
      GetRowRect(m_nPrevRow, &rc);
      InvalidateRect(&rc, FALSE);
   }
   GetRowRect(m_nCurRow, &rc);
   InvalidateRect(&rc, FALSE);
}

void CGridCtrl::GetCellRect(const POINT* cell, RECT* rc)
{
   assert (cell);
   assert (cell->x >= 0 && cell->x < m_nCols);
   assert (cell->y >= 0 && cell->y < m_nRows);
   assert (rc);

   if (cell->y == 0) {
      rc->top = 0;
      rc->bottom = m_nRowHeight;
   } else {
      rc->top = (cell->y - m_nFirstRow + 1)* m_nRowHeight;
      rc->bottom = rc->top + m_nRowHeight;
   }
   rc->left = m_pColPos[cell->x] - m_nHOffset;
   rc->right = m_pColPos[cell->x] - m_nHOffset;
   rc->left = rc->right - m_pColWidth[cell->x];
}

void CGridCtrl::GetRowRect(int nRow, RECT* rc)
{
   assert (::IsWindow(m_hWnd));

   RECT rcClient;
   GetClientRect(&rcClient);
   *rc = rcClient;

   rc->top = (nRow - m_nFirstRow + 1) * m_nRowHeight;
   rc->bottom = rc->top + m_nRowHeight;
}

LRESULT CGridCtrl::OnVKLeft()
{
   assert (m_nCurCol < m_nCols);

   if (m_nCurCol > 0) {
      int col = m_nCurCol - 1;
      POINT cell;
      cell.x = col;
      cell.y = m_nCurRow;
      SelectCell(cell);
   }

   return 0;
}

LRESULT CGridCtrl::OnVKRight()
{
   assert (m_nCurCol < m_nCols);

   if (m_nCurCol < 0) {
      return 0;
   }

   if (m_nCurCol < m_nCols-1) {
      int col = m_nCurCol +1;
      POINT cell;
      cell.x = col;
      cell.y = m_nCurRow;
      SelectCell(cell);
   }

   return 0;
}

LRESULT CGridCtrl::OnVKUp()
{
   assert (m_nCurRow < m_nRows);

   if (m_nCurRow > 1) {
      int row = m_nCurRow - 1;
      POINT cell;
      cell.x = m_nCurCol;
      cell.y = row;
      SelectCell(cell);
   }

   return 0;
}

LRESULT CGridCtrl::OnVKDown()
{
   assert (m_nCurRow < m_nRows);

   if (m_nCurRow <= 0) {
      return 0;
   }

   if (m_nCurRow < m_nRows-1) {
      int row = m_nCurRow +1;
      POINT cell;
      cell.x = m_nCurCol;
      cell.y = row;
      SelectCell(cell);
   }

   return 0;
}

LRESULT CGridCtrl::OnVKNext()
{
   RECT rc;
   int nMinY, nMaxY;

   GetClientRect(&rc);
   GetScrollRange(SB_VERT, &nMinY, &nMaxY);
   int nVisibleRows = (rc.bottom - rc.top) / m_nRowHeight -1; //Exculde the header
   m_nFirstRow += nVisibleRows;
   if (m_nFirstRow > nMaxY) {
      m_nFirstRow = nMaxY;
      m_nFirstRow ++;
   }
   SetScrollPos(SB_VERT, m_nFirstRow -1, TRUE);
   Invalidate(FALSE);
   return 0;
}

LRESULT CGridCtrl::OnVKPrior()
{
   RECT rc;
   int nMinY, nMaxY;

   GetClientRect(&rc);
   GetScrollRange(SB_VERT, &nMinY, &nMaxY);
   int nVisibleRows = (rc.bottom - rc.top) / m_nRowHeight -1; //Exculde the header
   m_nFirstRow -= nVisibleRows;
   if (m_nFirstRow < 1) {
	   m_nFirstRow = 1;
   }
   SetScrollPos(SB_VERT, m_nFirstRow -1, TRUE);
   Invalidate(FALSE);
   return 0;
}

void CGridCtrl::SetCurCell(int nRow, int nCol)
{
   assert (nRow > 0 && nRow < m_nRows);
   assert (nCol >= 0 && nCol< m_nCols);
   
   if (nRow == m_nCurRow && nCol == m_nCurCol) {
      return;
   }

   POINT cell;
   cell.x = nCol;
   cell.y = nRow;
   SelectCell(cell);
}

void CGridCtrl::GetCurCell(int* pnRow, int *pnCol)
{
   if (pnRow) {
      *pnRow = m_nCurRow;
   }

   if (pnCol) {
      *pnCol = m_nCurCol;
   }
}
